<script setup lang="ts">
import { getCurrentInstance, ref, reactive, nextTick } from 'vue'
const { proxy } = getCurrentInstance()

/* ---- */
const picker = reactive({
  isFirst: true,
  show: false,
  shade: false,
  box: false,
  spin: { r: 0, d: 0, itemAngle: 0, itemHeight: 0, D2rad: Math.PI / 180, singleDeg: 0, itemVisible: 90, maxExceed: 30 },
  items: [],
  date: ['年', '月', '日'],
  // ----
  type: 0,
  opts: {
    beginYear: 0,
    endYear: 0,
    date: ''
  },
  button: [],
  yes: '',
  no: ''
})

// ----
const open = (opt) => {
  if (opt) {
    for (const key in opt) { picker[key] = opt[key] }
  }
  picker.show = true
  // ----
  const curYear = new Date().getFullYear()
  if (Number(picker.opts.beginYear) === 0) picker.opts.beginYear = Number(curYear - 100)
  if (Number(picker.opts.endYear) === 0) {
    const forYear = (Number(picker.type) === 1) ? 0 : 100
    picker.opts.endYear = Number(curYear + forYear)
  }
  // ----
  nextTick(() => {
    picker.shade = true
    picker.box = true
    // ----
    setPickData()
  })
}
const setPickData = () => {
  const newItems = []
  let forY, forM, forD
  if (picker.opts.date && Number(picker.opts.date.length) > 0) {
    const now = picker.opts.date.split('-')
    forY = parseInt(now[0])
    forM = parseInt(now[1])
    forD = parseInt(now[2])
  } else {
    const now = new Date()
    forY = now.getFullYear()
    forM = now.getMonth() + 1
    forD = now.getDate()
  }
  picker.date.forEach((item) => {
    const sunItems = []
    switch (String(item)) {
      case '年': {
        const yBegin = picker.opts.beginYear
        const yEnd = picker.opts.endYear
        for (let y = yBegin; y <= yEnd; y++) {
          sunItems.push({
            text: y + '',
            value: y
          })
        }
        newItems.push({
          wUl: null,
          wLi: null,
          sUl: null,
          sLi: null,
          finger: { startY: 0, beginDeg: 0, lastDeg: 0, beginExceed: 0, endExceed: 0, againDeg: 0, moveDeg: 0, lastMoveStart: 0, lastMoveTime: 0, stopInertiaMove: false },
          curName: String(item),
          curDate: forY,
          pData: sunItems
        })
        break
      }
      case '月': {
        const mBegin = 1
        const mEnd = 12
        for (let m = mBegin; m <= mEnd; m++) {
          sunItems.push({
            text: fill(m),
            value: m
          })
        }
        newItems.push({
          wUl: null,
          wLi: null,
          sUl: null,
          sLi: null,
          finger: { startY: 0, beginDeg: 0, lastDeg: 0, beginExceed: 0, endExceed: 0, againDeg: 0, moveDeg: 0, lastMoveStart: 0, lastMoveTime: 0, stopInertiaMove: false },
          curName: String(item),
          curDate: forM,
          pData: sunItems
        })
        break
      }
    }
  })
  picker.items = newItems
  // ----
  nextTick(() => { createDay(forY, forM, forD) })
}
const createDay = (year, month, day) => {
  const newItems = []
  const dBegin = 1
  const dEnd = getDayNum(parseInt(year), parseInt(month))
  for (let d = dBegin; d <= dEnd; d++) {
    newItems.push({
      text: fill(d),
      value: d
    })
  }
  if (Number(dEnd) < Number(day)) day = dEnd
  if (picker.items[2]) {
    picker.items[2].curDate = parseInt(day)
    picker.items[2].pData = newItems
  } else {
    picker.items.push({
      wUl: null,
      wLi: null,
      sUl: null,
      sLi: null,
      finger: { startY: 0, beginDeg: 0, lastDeg: 0, beginExceed: 0, endExceed: 0, againDeg: 0, moveDeg: 0, lastMoveStart: 0, lastMoveTime: 0, stopInertiaMove: false },
      curName: '日',
      curDate: parseInt(day),
      pData: newItems
    })
  }
  // ----
  nextTick(() => { setPickInit() })
}
const setPickInit = () => {
  picker.items.forEach((item, index) => {
    if (item.pData.length > 0) {
      item.wUl = proxy.$refs.xwPicker.querySelectorAll('ul')[2 * index]
      item.wLi = item.wUl.querySelectorAll('li')
      item.sUl = proxy.$refs.xwPicker.querySelectorAll('ul')[2 * index + 1]
      item.sLi = item.sUl.querySelectorAll('li')
      if (Number(index) === Number(0) && picker.isFirst) {
        picker.spin.r = proxy.$refs.xwPicker.offsetHeight / 2 - 10
        picker.spin.d = picker.spin.r * 2
        picker.spin.itemHeight = item.wLi[0].offsetHeight
        picker.spin.itemAngle = parseInt(calcAngle(picker.spin.itemHeight))
        picker.spin.singleDeg = picker.spin.itemAngle / picker.spin.itemHeight
      }
      item.finger.lastDeg = Math.abs(picker.spin.itemAngle * (item.wLi.length - 1))
      item.finger.beginExceed = item.finger.beginDeg - picker.spin.maxExceed
      item.finger.endExceed = item.finger.lastDeg + picker.spin.maxExceed
      // ----
      let forNum = 0
      item.pData.some((curItem, num) => {
        if (Number(curItem.value) === Number(item.curDate)) forNum = num
      })
      item.finger.moveDeg = picker.spin.itemAngle * forNum
      // ----
      setWheelDeg(item.finger.moveDeg, item)
    }
  })
}
const calcAngle = (c) => {
  const a = parseFloat(picker.spin.r)
  const b = parseFloat(picker.spin.r)
  const d = parseFloat(picker.spin.d)
  const D2rad = parseFloat(picker.spin.D2rad)
  c = Math.abs(c)
  const intDeg = parseInt(c / d) * 180
  c = c % d
  const cosC = (a * a + b * b - c * c) / (2 * a * b)
  const angleC = intDeg + Math.acos(cosC) / D2rad
  return angleC
}
const setItemVisible = (item) => {
  const arr = item
  const angle = Math.abs(arr.finger.moveDeg)
  arr.wLi.forEach((item, index) => {
    const difference = Math.abs(picker.spin.itemAngle * index - angle)
    if (difference < picker.spin.itemVisible) {
      item.classList.add('visible')
      arr.sLi[index].classList.add('visible')
    } else {
      item.classList.remove('visible')
      arr.sLi[index].classList.remove('visible')
    }
  })
}
const setWheelItemDeg = (index) => {
  return { transform: `rotate3d(1, 0, 0, ${-index * picker.spin.itemAngle}deg) translate3d(0px, 0px, ${picker.spin.r}px)` }
}
const setWheelDeg = (updateDeg, item) => {
  item.finger.moveDeg = updateDeg
  item.wUl.style.webkitTransform = `rotate3d(1, 0, 0, ${updateDeg}deg)`
  item.sUl.style.webkitTransform = `rotate3d(1, 0, 0, ${updateDeg}deg)`
  setItemVisible(item)
}
// ----
const handleEvent = (item, sort, event) => {
  if (String(event.type) === 'touchstart') {
    item.wUl.style.webkitTransition = ''
    item.sUl.style.webkitTransition = ''
    item.finger.startY = event.changedTouches[0].pageY
    // ----
    updateInertiaParams(item, event, true)
  }
  if (String(event.type) === 'touchmove') {
    const endY = event.changedTouches[0].pageY
    const currentMove = item.finger.againDeg
    const move = endY - item.finger.startY
    const updateMove = move
    let updateDeg = -updateMove * picker.spin.singleDeg + currentMove
    if (updateDeg > item.finger.endExceed) {
      updateDeg = item.finger.endExceed
    }
    if (updateDeg < item.finger.beginExceed) {
      updateDeg = item.finger.beginExceed
    }
    setWheelDeg(updateDeg, item)
    // ----
    updateInertiaParams(item, event)
  }
  if (String(event.type) === 'touchend') {
    startInertiaScroll(item, sort, event)
  }
}
const updateInertiaParams = (item, event, isStart) => {
  const point = event.changedTouches[0]
  if (isStart) {
    item.finger.lastMoveStart = point.pageY
    item.finger.lastMoveTime = event.timeStamp || Date.now()
    item.finger.againDeg = item.finger.moveDeg
  } else {
    const nowTime = event.timeStamp || Date.now()
    if (nowTime - item.finger.lastMoveTime > 300) {
      item.finger.lastMoveStart = point.pageY
      item.finger.lastMoveTime = nowTime
    }
  }
  item.finger.stopInertiaMove = true
}
const startInertiaScroll = (item, sort, event) => {
  // --缓动代码--
  const nowTime = event.timeStamp || Date.now()
  // --最后一段时间手指划动速度--
  const v = (event.changedTouches[0].pageY - item.finger.lastMoveStart) / (nowTime - item.finger.lastMoveTime)
  // --加速度方向--
  const dir = v > 0 ? -1 : 1
  const deceleration = dir * 0.0006
  // --速度消减至0所需时间--
  let duration = Math.abs(v / deceleration)
  // --最终移动多少--
  const dist = v * duration / 2
  const inertiaDeg = item.finger.moveDeg
  let distAngle = calcAngle(dist) * dir
  // ----
  const srcDistDeg = distAngle
  if (inertiaDeg + distAngle < item.finger.beginExceed) {
    distAngle = item.finger.beginExceed - inertiaDeg
    duration = duration * (distAngle / srcDistDeg) * 0.6
  }
  if (inertiaDeg + distAngle > item.finger.endExceed) {
    distAngle = item.finger.endExceed - inertiaDeg
    duration = duration * (distAngle / srcDistDeg) * 0.6
  }
  if (Number(distAngle) === Number(0)) {
    endScroll(item, sort)
    return
  }
  // ----
  scrollDistAngle(inertiaDeg, distAngle, duration, item, sort)
}
const scrollDistAngle = (inertiaDeg, distAngle, duration, item, sort) => {
  item.finger.stopInertiaMove = false
  ;((inertiaDeg, distAngle, duration) => {
    const frameInterval = 10
    const stepCount = duration / frameInterval
    let stepIndex = 0
    const inertiaMove = () => {
      if (item.finger.stopInertiaMove) { return }
      const newAngle = quartEaseOut(stepIndex, inertiaDeg, distAngle, stepCount)
      setWheelDeg(newAngle, item)
      stepIndex++
      if (stepIndex > stepCount - 1 || newAngle < item.finger.beginExceed || newAngle > item.finger.endExceed) {
        endScroll(item, sort)
        return
      }
      setTimeout(inertiaMove, frameInterval)
    }
    inertiaMove()
  })(inertiaDeg, distAngle, duration)
}
const quartEaseOut = (t, b, c, d) => {
  return -c * ((t = t / d - 1) * t * t * t - 1) + b
}
const endScroll = (item, sort) => {
  item.wUl.style.webkitTransition = '150ms ease-out'
  item.sUl.style.webkitTransition = '150ms ease-out'
  const index = getSelectedIndex(item)
  let updateDeg = 0
  if (item.finger.moveDeg < item.finger.beginDeg) {
    item.finger.moveDeg = item.finger.beginDeg
    updateDeg = item.finger.beginDeg
  } else if (item.finger.moveDeg > item.finger.lastDeg) {
    item.finger.moveDeg = item.finger.lastDeg
    updateDeg = item.finger.lastDeg
  } else { updateDeg = picker.spin.itemAngle * index }
  setWheelDeg(updateDeg, item)
  // ----
  nextTick(() => {
    const curIndex = getSelectedIndex(item)
    item.curDate = item.pData[curIndex].value
    createDay(picker.items[0].curDate, picker.items[1].curDate, picker.items[2].curDate)
  })
}
// ----
const getResultData = () => {
  const resData = []
  picker.items.forEach((item, index) => {
    if (item.pData.length > 0) {
      resData.push(item.pData[getSelectedIndex(item)])
    }
  })
  return resData
}
const getSelectedIndex = (item) => {
  return parseInt((item.finger.moveDeg / picker.spin.itemAngle).toFixed(0))
}
const sureBtn = (index) => {
  if (picker.button.length > 1) {
    if (Number(index) === Number(0)) {
      picker.no && picker.no()
    } else {
      picker.yes && picker.yes(getResultData())
    }
  }
  close()
}
const close = () => {
  picker.isFirst = false
  picker.shade = false
  picker.box = false
  // ----
  setTimeout(() => { picker.show = false }, 300)
}
const maskClose = () => { close() }
// ----
const isLeapYear = (year) => {
  return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0)
}
const inArray = (array, item) => {
  for (const key in array) {
    const _item = array[key]
    if (_item === item) return true
  }
  return false
}
const getDayNum = (year, month) => {
  if (inArray([1, 3, 5, 7, 8, 10, 12], month)) {
    return 31
  } else if (inArray([4, 6, 9, 11], month)) {
    return 30
  } else if (isLeapYear(year)) {
    return 29
  } else {
    return 28
  }
}
const fill = (num) => {
  num = num.toString()
  if (num.length < 2) num = 0 + num
  return num
}

/* ---- */
defineExpose({
  open
})
</script>

<template>
  <div class="pickerBox" v-if="picker.show">
    <transition name="fade">
      <div class="mask" v-if="picker.shade" @click="maskClose"></div>
    </transition>
    <transition name="slidDownUp">
      <div class="selbox" v-if="picker.box">
        <div class="SEB-btn xw-flex xw-flex-align xw-flex-justify">
          <span v-for="(item, index) in picker.button" :class="{'yes' : index > 0}" class="xw-radius-3 xw-act" @click="sureBtn(index)">{{item}}</span>
        </div>
        <div class="SEB-top xw-Bfg xw-flex xw-flex-align">
          <span class="xw-flex-W1" v-for="(value, index) in picker.date">{{value}}</span>
        </div>
        <div class="SEB-body xw-flex" ref="xwPicker">
          <div class="xw-flex-W1" v-for="(item, index) in picker.items">
            <ul class="SEB-wheel">
              <li class="xw-ellipsis" v-for="(son, num) in item.pData" :style="setWheelItemDeg(num)">{{son.text}}</li>
            </ul>
            <div class="SEB-list">
              <ul class="SEB-list-ul">
                <li class="xw-ellipsis" v-for="(son, num) in item.pData" :style="setWheelItemDeg(num)">{{son.text}}</li>
              </ul>
            </div>
            <div class="SEB-line"></div>
            <div class="SEB-scroll" @touchstart.prevent="handleEvent(item, index, $event)" @touchmove.prevent="handleEvent(item, index, $event)" @touchend.prevent="handleEvent(item, index, $event)"></div>
          </div>
        </div>
      </div>
    </transition>
  </div>
</template>

<style scoped>
/* ---- */
.pickerBox, .mask { position: fixed; z-index: 999; top: 0; left: 0; right: 0; bottom: 0; }
.mask { background: rgba(0, 0, 0, 0.5); }
/* ---- */
.selbox { position: fixed; left: 0; right: 0; bottom: 0; background: #eeeeee; -webkit-box-shadow: 0 0 1.4rem rgba(0, 0, 0, 0.4); box-shadow: 0 0 1.4rem rgba(0, 0, 0, 0.4); z-index: 999; }
.SEB-btn { padding: 0.8rem 0 0.8rem 1rem; }
.SEB-btn span { margin: 0 1rem 0 0; padding: 0.6rem 1.2rem; background: #ffffff; border: 1px solid #bbbbbb; }
.SEB-btn span.yes { background: #3388ff; border: 1px solid #3388ff; color: #ffffff; }
.SEB-top { padding: 1rem 0; text-align: center; font-size: 1.5rem; background: #dddddd; }
.SEB-body { height: 20rem; background: #dddddd; }
.SEB-body .xw-flex-W1 { position: relative; }
/* ---- */
.SEB-wheel { position: absolute; margin-top: -2rem; left: 0; right: 0; top: 50%; height: 4rem; line-height: 4rem; overflow: visible; transform-style: preserve-3d; transform: rotate3d(0, 0, 0, 0); }
.SEB-wheel li { position: absolute; width: 100%; line-height: 4rem; text-align: center; font-size: 1.6rem; visibility: hidden; -webkit-backface-visibility: hidden; backface-visibility: hidden; color: #999999; }
.SEB-wheel li.active, .SEB-wheel li.visible { visibility: visible; }
/* ---- */
.SEB-list { position: relative; top: 50%; right: 0; left: 0; height: 4rem; background: #f0f0f0; transform: translate3d(0, -50%, 1000px); }
.SEB-list-ul { position: absolute; top: 0; right: 0; bottom: 0; left: 0; overflow: visible; transform-style: preserve-3d; transform: rotate3d(0, 0, 0, 0); }
.SEB-list-ul li { position: absolute; width: 100%; line-height: 4rem; text-align: center; font-size: 1.6rem; visibility: hidden; -webkit-backface-visibility: hidden; backface-visibility: hidden; }
.SEB-list-ul li.active, .SEB-list-ul li.visible { visibility: visible; }
/* ---- */
.SEB-line { position: absolute; width: 100%; height: 4rem; top: 50%; border-top: 1px solid rgba(0, 0, 0, 0.2); border-bottom: 1px solid rgba(0, 0, 0, 0.2); transform: translate3d(0, -50%, 1000px); }
/* ---- */
.SEB-scroll { position: absolute; top: 0; right: 0; bottom: 0; left: 0; transform: translateZ(1000px); }

/* ---- */
.easein { transition: transform .1s ease-out; }
</style>
